#include "config.h"
#include "system.h"
#include "ansidecl.h"
#include "coretypes.h"
#include "tm.h"
#include "opts.h"
#include "tree.h"
#include "tree-iterator.h"
#include "tree-pass.h"
#include "gimple.h"
#include "toplev.h"
#include "debug.h"
#include "options.h"
#include "flags.h"
#include "convert.h"
#include "diagnostic-core.h"
#include "langhooks.h"
#include "langhooks-def.h"
#include "target.h"
#include "cgraph.h"
#include "vec.h"

#include "js.h"
#include "js-lang.h"
#include "js-tree.h"
#include "js-utils.h"
#include "js-builtin.h"

static js_hashmap_t *js_built_in_decls = NULL;
static void generate_default_type_struct(void);
tree js_global_trees[JSTREE_MAX];

tree js_get_builtin_function_decl(const char *name)
{
  return (tree) js_hashmap_get(js_built_in_decls, name);
}

enum jstree_index match_js_global_trees(tree t)
{
    int i;
    for (i = 0; i < JSTREE_MAX; ++i) {
        if (js_global_trees[i] == t) {
            return i;
        }
    }
    return -1;
}

#define BUILTIN_NOTHROW 1
#define BUILTIN_CONST 2
static void define_builtin(const char *name, const char *realname, tree type, int flags)
{
  tree decl = build_decl(BUILTINS_LOCATION, FUNCTION_DECL,
                         get_identifier(name), type);
  DECL_EXTERNAL(decl) = true;
  TREE_PUBLIC(decl)   = true;
  SET_DECL_ASSEMBLER_NAME(decl, get_identifier(realname));
  //DECL_BUILT_IN_CLASS(decl) = BUILT_IN_NORMAL;
  if (flags & BUILTIN_NOTHROW) {
      TREE_NOTHROW(decl) = true;
  }
  js_hashmap_set(js_built_in_decls, name, (void*)decl);
}

struct builtin_def {
    const char *name;
    const char *real_name;
    js_type rettype;
    int argc;
    js_type args[5];
};
struct builtin_def builtins[] = {
      {"__init__", "__init__", JS_VOID, 2, {JS_INT, JS_RAWSTR}},
      {"__entrypoint__", "__entrypoint__", JS_VOID, 1, {JS_REF}},
      {"abort", "abort", JS_VOID, 0, {JS_VOID}},
      {"print", "print", JS_VOID, 4, {JS_INT/*self*/, JS_INT, JS_VARGS, JS_VOID}},
      {"typeof", "typeof", JS_STRING, 1, {JS_OBJECT}},
      {"get", "Object_get", JS_OBJECT, 2, {JS_OBJECT, JS_STRING}},
      {"set", "Object_set", JS_VOID, 3, {JS_OBJECT, JS_STRING, JS_OBJECT}},
      {"object_new", "Object_new", JS_OBJECT, 1, {JS_INT}},
      {"get_prototype", "Object_get_prototype", JS_OBJECT, 1, {JS_OBJECT}},
      {"set_prototype", "Object_set_prototype", JS_VOID, 2, {JS_OBJECT, JS_OBJECT}},

      /* Number */
      {"int_new", "int_new", JS_INTOBJ, 1, {JS_INT}},
      {"float_new", "float_new", JS_FLTOBJ, 1, {JS_FLOAT}},
      {"int_add",  "Int_opAdd", JS_INTOBJ, 2, {JS_INTOBJ, JS_INTOBJ}},
      {"int_sub",  "Int_opSub", JS_INTOBJ, 2, {JS_INTOBJ, JS_INTOBJ}},
      {"int_mul",  "Int_opMul", JS_INTOBJ, 2, {JS_INTOBJ, JS_INTOBJ}},
      {"int_div",  "Int_opDiv", JS_INTOBJ, 2, {JS_INTOBJ, JS_INTOBJ}},
      {"int_eq",   "Int_opEQ",    JS_BOOL, 2, {JS_INTOBJ, JS_INTOBJ}},
      {"int_neq",  "Int_opNEQ",   JS_BOOL, 2, {JS_INTOBJ, JS_INTOBJ}},
      {"int_lt",   "Int_opLT",   JS_BOOL, 2, {JS_INTOBJ, JS_INTOBJ}},
      {"int_gt",   "Int_opGT",   JS_BOOL, 2, {JS_INTOBJ, JS_INTOBJ}},
      {"int_mod",   "Int_opMod",   JS_INTOBJ, 2, {JS_INTOBJ, JS_INTOBJ}},

      {"float_add",  "Float_opAdd", JS_FLTOBJ, 2, {JS_FLTOBJ, JS_FLTOBJ}},
      {"float_sub",  "Float_opSub", JS_FLTOBJ, 2, {JS_FLTOBJ, JS_FLTOBJ}},
      {"float_mul",  "Float_opMul", JS_FLTOBJ, 2, {JS_FLTOBJ, JS_FLTOBJ}},
      {"float_div",  "Float_opDiv", JS_FLTOBJ, 2, {JS_FLTOBJ, JS_FLTOBJ}},
      {"float_eq",   "Float_eq",    JS_BOOL,   2, {JS_FLTOBJ, JS_FLTOBJ}},
      {"float_neq",  "Float_neq",   JS_BOOL,   2, {JS_FLTOBJ, JS_FLTOBJ}},
      {"float_lt",   "Float_opLT",  JS_BOOL,   2, {JS_FLTOBJ, JS_FLTOBJ}},
      {"float_gt",   "Float_opGT",  JS_BOOL,   2, {JS_FLTOBJ, JS_FLTOBJ}},

      /* Array */
      {"Array_new", "Array_new", JS_ARRAY, 1, {JS_INT, JS_VOID}},
      /* String */
      {"static_new_string", "static_new_String", JS_STRING, 2, {JS_RAWSTR, JS_INT}},
      {"length", "String_length", JS_INT, 1, {JS_STRING}},
      {"string_eq",   "String_eq", JS_BOOL, 2, {JS_STRING, JS_STRING}},
      {"string_neq",  "String_neq", JS_BOOL, 2, {JS_STRING, JS_STRING}},
      {"string_plus", "String_opPlus", JS_STRING, 2, {JS_STRING, JS_STRING}},
      {"string_toString", "String_toString", JS_STRING, 1, {JS_STRING}},
      {"string_valueOf", "String_valueOf", JS_STRING, 1, {JS_STRING}},
      {"string_concat", "String_concat", JS_STRING, 3, {JS_INT, JS_VARGS, JS_VOID}},
      {"string_slice", "String_slice", JS_STRING, 2, {JS_INT, JS_INT}},
      {"string_substring", "String_substring", JS_STRING, 2, {JS_INT, JS_INT}},
      {"string_substr", "String_substr", JS_STRING, 2, {JS_INT, JS_INT}},
      {"charAt", "String_charAt", JS_STRING, 2, {JS_STRING, JS_INT}},
      {"charCodeAt", "String_charCodeAt", JS_INT, 2, {JS_STRING, JS_INT}},
      {"fromCharCode", "String_fromCharCode", JS_STRING, 1, {JS_INT}},
      {"toLowerCase", "String_toLowerCase", JS_STRING, 1, {JS_STRING}},
      {"toUpperCase", "String_toUpperCase", JS_STRING, 1, {JS_STRING}},
      {"indexOf", "String_indexOf", JS_INT, 2, {JS_STRING, JS_STRING}},
      {"lastIndexOf", "String_lastIndexOf", JS_INT, 2, {JS_STRING, JS_STRING}},
      /* Function */
      {"function_new",  "new_Function", JS_FUNCTION, 1, {JS_REF}},
      {"function_call", "Function_call", JS_OBJECT, 1, {JS_FUNCTION, JS_INTOBJ, JS_VARGS}},
      /* Boolean */
      {"boolean_toString", "Boolean_toString", JS_STRING, 1, {JS_BOOL}},
      {"boolean_valueOf", "Boolean_valueOf", JS_BOOL, 1, {JS_BOOL}},
      /* Math */
      {"Math_abs"   ,"Math_abs"   , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_acos"  ,"Math_acos"  , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_asin"  ,"Math_asin"  , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_atan"  ,"Math_atan"  , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_atan2" ,"Math_atan2" , JS_FLOAT, 2, {JS_FLOAT, JS_FLOAT}},
      {"Math_ceil"  ,"Math_ceil"  , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_cos"   ,"Math_cos"   , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_exp"   ,"Math_exp"   , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_floor" ,"Math_floor" , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_log"   ,"Math_log"   , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_max"   ,"Math_max"   , JS_FLOAT, 3, {JS_INT, JS_VARGS, JS_VOID}},
      {"Math_min"   ,"Math_min"   , JS_FLOAT, 3, {JS_INT, JS_VARGS, JS_VOID}},
      {"Math_pow"   ,"Math_pow"   , JS_FLOAT, 2, {JS_FLOAT, JS_FLOAT}},
      {"Math_random","Math_random", JS_FLOAT, 0, {JS_VOID}},
      {"Math_round" ,"Math_round" , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_sin"   ,"Math_sin"   , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_sqrt"  ,"Math_sqrt"  , JS_FLOAT, 1, {JS_FLOAT}},
      {"Math_tan"   ,"Math_tan"   , JS_FLOAT, 1, {JS_FLOAT}},
};

static tree type_to_tree(js_type type)
{
  switch(type) {
    case JS_OBJECT:
      return object_type_ptr;
    case JS_BOOL:
      return boolean_type_node;
    case JS_INT:
      return integer_type_node;
    case JS_FLOAT:
      return double_type_node;
    case JS_STRING:
      return obj_string_type_ptr;
    case JS_ARRAY:
      return array_type_ptr;
    case JS_FUNCTION:
      return function_type_ptr;
    case JS_INTOBJ:
      return intobj_type_ptr;
    case JS_FLTOBJ:
      return floatobj_type_ptr;
    case JS_VOID:
      return void_type_node;
    case JS_VARGS:
      return va_list_type_node;
    case JS_RAWSTR:
      return string_type_node;
    case JS_REF:
      return ptr_type_node;
    case JS_UNDEF:
    default:
      error("undefined type");
      exit(EXIT_FAILURE);
  }
}

#define _ARRAY_SIZE(a) ((int)(sizeof(a) / sizeof((a)[0])))
void init_builtin_functions(void)
{
  VEC(tree,gc) *args;
  int i, j;
  js_built_in_decls = js_hashmap_new(131);
  for (i = 0; i < _ARRAY_SIZE(builtins); i++) {
      tree type, rettype;
      char buf[32] = {0};
      struct builtin_def *def = &builtins[i];
      args = VEC_alloc(tree,gc, 0);
      for (j = 0; j < def->argc; j++) {
          type = type_to_tree(def->args[j]);
          VEC_safe_push(tree, gc, args, type);
      }
      snprintf(buf, 32, "js_runtime_%s", def->real_name);
      rettype = type_to_tree(def->rettype);
      type = js_build_function_type(rettype, args);
      define_builtin(def->name, buf, type, BUILTIN_NOTHROW);
  }
}

void init_builtins(void)
{
  /* base types decl */
  build_common_tree_nodes (false);

  if (TYPE_MODE (long_unsigned_type_node) == ptr_mode)
      size_type_node = long_unsigned_type_node;
  else if (TYPE_MODE (long_long_unsigned_type_node) == ptr_mode)
      size_type_node = long_long_unsigned_type_node;
  else
      size_type_node = long_unsigned_type_node;
  set_sizetype (size_type_node);

  build_common_tree_nodes_2 (0);
  generate_default_type_struct();


  set_sizetype (size_type_node);
  /* construct builtin functions */
  init_builtin_functions();
  using_eh_for_cleanups();
}

static tree js_start_struct(tree name ATTRIBUTE_UNUSED)
{
  tree type = make_node(RECORD_TYPE);
  return type;
}

static tree js_finish_struct(tree type)
{
  layout_type(type);
  return type;
}

static tree js_add_field_decl(tree type, tree field, const char *name, tree ftype)
{
  tree decl = build_decl(BUILTINS_LOCATION, FIELD_DECL,
                         get_identifier(name), ftype);
  if (TYPE_FIELDS(type) == NULL_TREE)
      TYPE_FIELDS(type) = decl;
  else {
      DECL_CHAIN(field) = decl;
  }
  DECL_CONTEXT(decl) = type;
  return decl;
}

static void generate_default_type_struct(void)
{
  tree type, field, name;
  js_init_node = js_build_id("__init__");
  abort_node = js_build_id("abort");
  this_node = js_build_id("this");
  getter_node = js_build_id("get");
  setter_node = js_build_id("set");
  get_prototype_node = js_build_id("get_prototype");
  set_prototype_node = js_build_id("set_prototype");
  string_length_node = js_build_id("length");
  new_int_node   = js_build_id("int_new");
  new_float_node = js_build_id("float_new");
  new_node = js_build_id("object_new");
  array_new_node = js_build_id("Array_new");
  static_string_new_node = js_build_id("static_new_string");
  function_new_node = js_build_id("function_new");
  function_call_node = js_build_id("function_call");

  call_entry_node = js_build_id("__entrypoint__");

  /* object */
  name = js_build_id("object");
  type = js_start_struct(name);
  field = js_add_field_decl(type, NULL_TREE, "type", integer_type_node);
  field = js_add_field_decl(type, field, "prototype", ptr_type_node);
  field = js_add_field_decl(type, field, "unused", ptr_type_node);
  js_finish_struct(type);
  object_type_node = type;
  object_type_ptr  = build_pointer_type(type);
  layout_type(object_type_node);
  layout_type(object_type_ptr);

  /* Int */
  name = js_build_id("Int");
  type = js_start_struct(name);
  field = js_add_field_decl(type, NULL_TREE, "type", integer_type_node);
  field = js_add_field_decl(type, field, "prototype", ptr_type_node);
  field = js_add_field_decl(type, field, "n", integer_type_node);
  js_finish_struct(type);
  intobj_type_node = type;
  intobj_type_ptr  = build_pointer_type(type);
  layout_type(intobj_type_node);
  layout_type(intobj_type_ptr);

  /* Float */
  name = js_build_id("Float");
  type = js_start_struct(name);
  field = js_add_field_decl(type, NULL_TREE, "type", integer_type_node);
  field = js_add_field_decl(type, field, "prototype", ptr_type_node);
  field = js_add_field_decl(type, field, "n", double_type_node);
  js_finish_struct(type);
  floatobj_type_node = type;
  floatobj_type_ptr  = build_pointer_type(type);
  layout_type(floatobj_type_node);
  layout_type(floatobj_type_ptr);

  /* raw string */
  string_type_node = build_pointer_type(char_type_node);
  /* string object */
  name = js_build_id("string");
  type = js_start_struct(name);
  field = js_add_field_decl(type, NULL_TREE, "type", integer_type_node);
  field = js_add_field_decl(type, field, "prototype", ptr_type_node);
  field = js_add_field_decl(type, field, "length", integer_type_node);
  field = js_add_field_decl(type, field, "__str", string_type_node);
  js_finish_struct(type);

  obj_string_type_node = type;
  obj_string_type_ptr  = build_pointer_type(type);
  layout_type(obj_string_type_node);
  layout_type(obj_string_type_ptr);

  /* function */
  name = js_build_id("function");
  type = js_start_struct(name);
  field = js_add_field_decl(type, NULL_TREE, "type", integer_type_node);
  field = js_add_field_decl(type, field, "prototype", ptr_type_node);
  field = js_add_field_decl(type, field, "func", ptr_type_node);
  js_finish_struct(type);
  function_type_node = type;
  function_type_ptr  = build_pointer_type(type);
  layout_type(function_type_node);
  layout_type(function_type_ptr);

  integer_type_ptr = build_pointer_type(integer_type_node);
  /* array object */
  name = js_build_id("array");
  type = js_start_struct(name);
  field = js_add_field_decl(type, NULL_TREE, "type", integer_type_node);
  field = js_add_field_decl(type, field, "prototype", ptr_type_node);
  field = js_add_field_decl(type, field, "length", integer_type_node);
  field = js_add_field_decl(type, field, "capacity", integer_type_node);
  field = js_add_field_decl(type, field, "__list", ptr_type_node);
  js_finish_struct(type);
  array_type_node = type;
  array_type_ptr  = build_pointer_type(type);
  layout_type(array_type_node);
  layout_type(array_type_ptr);
}

